#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright 2007 Denis Kürov
# Licensed under the GNU General Public License, version 3.
# See the file http://www.gnu.org/copyleft/gpl.txt.


from iptables.models import Rule,Chain,Loadindex
from confplace import FWALL_PATH,NL_GROUP

import os #for rule execution

class RuleGenerator(object):
    """ The class will generate the iptables rule according to db rules"""
    
    def __init__(self):
        #that is the final rule
        self.__ready_rule=""
        self.__ruleid=None
        
    def generate_generic(self,rule):
        """ That one generates the generic part of the rule which is :
        ip generation :
            --source [!] (value)
            --destination [!] (value)
            -m iprange [!] --src-range (value)
            -m iprange [!] --dst-range (value)
            
        interface generation:
            -i [!] (value)
            -o [!] (value)"""
            
        temp_gen=""
        
        #port icin cikabilecek siklar a,r,s,n   
        ip=rule.sip.split(',')
        
        ip[0]=ip[0].strip()
        #if not any chosen
        if not ip[0]=='a':
            
            if ip[0]=='s' or ip[0]=='n':
                #if single or a netmask value
                if not len(ip)==3:
                    temp_gen="".join(['--source ',ip[len(ip)-1].strip()])
        
                else:
                    temp_gen="".join(['--source ! ',ip[len(ip)-1].strip()])
            else:
                #it means that it is a range option
                if not len(ip)==3:
                    temp_gen="".join(['-m iprange --src-range ',ip[len(ip)-1].strip()])
                else:
                    temp_gen="".join(['-m iprange ! --src-range ',ip[len(ip)-1].strip()])
                    
        
        
        ip=rule.dip.split(',')
        
        if not ip[0]=='a':
            
            if ip[0]=='s' or ip[0]=='n':
                #if single or a netmask value
                if not len(ip)==3:
                    temp_gen="".join([temp_gen,' --destination ',ip[len(ip)-1].strip()])
        
                else:
                    temp_gen="".join([temp_gen,' --destination ! ',ip[len(ip)-1].strip()])
            else:
                #it means that it is a range option
                if not len(ip)==3:
                    temp_gen="".join([temp_gen,' -m iprange --dst-range ',ip[len(ip)-1].strip()])
                else:
                    temp_gen="".join([temp_gen,' -m iprange ! --dst-range ',ip[len(ip)-1].strip()])
        
            
        
        if not rule.s_interface=='any':
            temp_gen="".join([temp_gen,' -i ',rule.s_interface.strip()])
            
        if not rule.d_interface=='any':
            temp_gen="".join([temp_gen,' -o ',rule.d_interface.strip()])
            
        self.__ready_rule=self.__ready_rule.strip()    
        self.__ready_rule="".join([self.__ready_rule," ",temp_gen.strip()])
        
        # do not forget to strip it at the end please
        return True
        
    def generate_tcp(self,tcp):
        """ That one generate the tcp part of the rule the schema is :
        -p tcp
        
        port match :
            --sport [!] (value)(single or netmasked)
            --dport [!] (value)
            -m multiport [!] --src-range (values)
            -m multiport [!] --dst-range (values)
        
        tcp-flag:
            --tcp-flags (values)
        """
        temp_tcp=""
        
        if not self.generate_udp(tcp):#it is same with that part yuup
            return False
        
        if not tcp.tcp_flag=="0":
            temp_tcp="".join(["--tcp-flags ",tcp.tcp_flag])
        
        
        self.__ready_rule=self.__ready_rule.strip()
        self.__ready_rule="".join([self.__ready_rule," ",temp_tcp.strip()])
        
        return True
    
    def generate_udp(self,udp):
        """ That one generates the udp part of the rule
        -p tcp
        
        port match :
            --sport [!] (value)(single or netmasked)
            --dport [!] (value)
            -m multiport [!] --src-range (values)
            -m multiport [!] --dst-range (values)
        """
        temp_udp=""
        
        port=udp.sport.split('*')
        
        port[0]=port[0].strip()
        
        if not port[0]=='a':
            
            if port[0]=='r' or port[0]=='s':
                if not len(port)==3:
                    if port[0]=='r':
                        ranger=":".join(str(port[len(port)-1]).strip().split("-"))
                        temp_udp="".join([" -m multiport --sport ",ranger])
                    else:
                        temp_udp="".join([" -m multiport --sport ",port[len(port)-1].strip()])
                else:
                    if port[0]=='r':
                        ranger=":".join(str(port[len(port)-1]).strip().split("-"))
                    
                        temp_udp="".join([" -m multiport ! --sport ",ranger])
                    else:
                        temp_udp="".join([" -m multiport ! --sport ",port[len(port)-1].strip()])
                    
            else:
                #it is a multiport option
                if not len(port)==3:
                    temp_udp="".join([" -m multiport --sport ",port[len(port)-1].strip()])
                else:
                    temp_udp="".join([" -m multiport ! --sport ",port[len(port)-1].strip()])
        
        port=udp.dport.split('*')
        
        port[0]=port[0].strip()
        
        if not port[0]=='a':
            
            if port[0]=='r' or port[0]=='s':
                if not len(port)==3:
                    if port[0]=='r':
                        ranger=":".join(str(port[len(port)-1]).strip().split("-"))
                        temp_udp="".join([temp_udp," -m multiport --dport ",ranger])
                    else:
                        temp_udp="".join([temp_udp," -m multiport --dport ",port[len(port)-1].strip()])
                else:
                    if port[0]=='r':
                        ranger=":".join(str(port[len(port)-1]).strip().split("-"))
                    
                        temp_udp="".join([temp_udp," -m multiport ! --dport ",ranger])
                        
                    else:
                        temp_udp="".join([temp_udp," -m multiport ! --dport ",port[len(port)-1].strip()])
                    
            else:
                #it is a multiport option
                if not len(port)==3:
                    temp_udp="".join([temp_udp," -m multiport --dport ",port[len(port)-1].strip()])
                else:
                    temp_udp="".join([temp_udp," -m multiport ! --dport ",port[len(port)-1].strip()])
        
        self.__ready_rule=self.__ready_rule.strip()
        self.__ready_rule="".join([self.__ready_rule," ",temp_udp.strip()])
        
        return True
        
    def generate_icmp(self,icmp):
        """ That one generates the icmp part of the rule
        -p icmp
        
        --icmp-type [!] (value)
        
        """
        temp_icmp=""
        
        temp_data=icmp.icmp_type.split('*')
    
        if len(temp_data)==2:
            temp_icmp="".join(["--icmp-type ! ",temp_data[1]])
            
            
        else:
            temp_icmp="".join(["--icmp-type ",temp_data[0]])
        
        self.__ready_rule=self.__ready_rule.strip()
        self.__ready_rule="".join([self.__ready_rule," ",temp_icmp.strip()])
        
        return True
            
    def generate_extra(self,extra):
        """ That one generates the icmp part of the rule
        
        conntrack:
            -m conntrack [!] --ctstatus (values) :b_takip
            
        length:
            -m length [!] --length (value:value) :paket limit
            
        limit:
            -m limit --limit (value) --limit-burst (value) :baglanti limit
            
        mac :
            -m mac --mac-source [!] (value) :mac limit
            
        state:
            -m conntrack [!] --ctstate (value) :b durum
        """
        
        temp_extra=""
        
        #status kontrol once
        temp_data=extra.ctstaus
        
        if not temp_data=="0":
            temp_data=temp_data.split("*")
            
            if len(temp_data)==2:
                #it means that it is inverted
                temp_extra="".join(["-m conntrack ! --ctstate ",temp_data[1].strip()])
                
            else:
                temp_extra="".join(["-m conntrack --ctstate ",temp_data[0].strip()])
        
        #the state
        temp_data=extra.state
    
        if not temp_data=="0":        
            temp_data=temp_data.split("*")
            
            if len(temp_data)==2:
                temp_extra="".join([temp_extra," -m conntrack ! --ctstatus ",temp_data[1].strip()])
                
            else:
                temp_extra="".join([temp_extra," -m conntrack --ctstatus ",temp_data[0].strip()])
                
        #the packet length
        temp_data=extra.mlength
                
        if not temp_data=="0":
            
            temp_data=temp_data.split("*")
            
            if len(temp_data)==2:
                temp_extra="".join([temp_extra," -m length ! --length ",temp_data[1].strip()])
                
            else:
                temp_extra="".join([temp_extra," -m length --length ",temp_data[0].strip()])
                
        #connection limit part is here
        temp_data=extra.mlimit
        
        if not temp_data=="0":
            
            temp_data=temp_data.split("*")
            
            if len(temp_data)==2:
                temp_extra="".join([temp_extra," -m limit --limit ",temp_data[1].strip()," --limit-burst ",temp_data[0].strip()])
             
            else:   
                temp_extra="".join([temp_extra," -m limit --limit ",temp_data[0].strip()])
    
        temp_data=extra.mac
        
        if not temp_data=="0":  
            temp_data=temp_data.split("*")
        
            if len(temp_data)==2: 
                temp_extra="".join([temp_extra," -m mac --mac-source ! ",temp_data[1].strip()])
                
            else:
                temp_extra="".join([temp_extra," -m mac --mac-source ",temp_data[0].strip()])
                
        self.__ready_rule=self.__ready_rule.strip()
        self.__ready_rule="".join([self.__ready_rule," ",temp_extra.strip()])
                
        return True
        
    def reset(self):
        """ Resets the rule string + current id"""
        self.__ruleid=None
        self.__ready_rule=""
    
    def set_rule_id(self,id):
        """ Sets from outside baceuse it is private"""
        self.__ruleid=id
        
    def get_rule_id(self):
        """ Getting it back"""
        return self.__ruleid.strip()
        
    def get_rule_str(self):
        """ Returns the current state of it"""
        return self.__ready_rule.strip()

    def set_rule_chain(self,r):
        """ Sets the rules chain"""
        self.__ready_rule=self.__ready_rule.strip()
        self.__ready_rule="".join([self.__ready_rule," ",r.parent_chain.name])
        
        
    def set_rule_target(self,r):
        """ Sets the rules target"""
        self.__ready_rule=self.__ready_rule.strip()
        self.__ready_rule="".join([self.__ready_rule," -j ",r.parent_target.name])
        
    
    def set_rule_action(self,action):
        """ Sets the ones that are permitted
        --append  -A chain            Append to chain
        --delete  -D chain            Delete matching rule from chain
        --delete  -D chain rulenum
                                Delete rule rulenum (1 = first) from chain
        --insert  -I chain [rulenum]
                                Insert in chain as rulenum (default 1=first)
        --replace -R chain rulenum
                                Replace rule rulenum (1 = first) in chain
        --list    -L [chain]          List the rules in a chain or all chains
        --flush   -F [chain]          Delete all rules in  chain or all chains
        --zero    -Z [chain]          Zero counters in chain or all chains
        --new     -N chain            Create a new user-defined chain
        --delete-chain -X [chain]          Delete a user-defined chain
        --rename-chain
            -E old-chain new-chain
        """
        actions={'append':'-A',
                 'delete':'-D',
                 'insert':'-I',
                 'list':'-L',
                 'flush':'-F',
                 'new':'-N',
                 'policy':'-P',
                 'del_c':'-X'
                 }
        
        if not actions.has_key(action):
            return False
        
        self.__ready_rule=self.__ready_rule.strip()
        self.__ready_rule="".join([self.__ready_rule," ",actions[action]])
        
        return True
        
    
    def main_gen(self,r):
        """ That one calls the others and controls the available options..."""
        """Also returns true or false if something happens"""
        
        
        #once generic kismi cagir ...
        if not self.generate_generic(r):
            return False
        
        #tcp kismi cagir
        if r.tcp_set.count()!=0:
            self.__ready_rule="".join([self.__ready_rule," -p tcp"])
            if not self.generate_tcp(r.tcp_set.get(parent_rule=r.id)):
                return False
            
        
        #udp kismi cagir
        if r.udp_set.count()!=0:
            self.__ready_rule="".join([self.__ready_rule," -p udp"])
            if not self.generate_udp(r.udp_set.get(parent_rule=r.id)):
                return False
        
        
        #icmp cagir
        if r.icmp_set.count()!=0:
            self.__ready_rule="".join([self.__ready_rule," -p icmp"])
            if not self.generate_icmp(r.icmp_set.get(parent_rule=r.id)):
                return False
        
        
        #extra cagir
        if r.optional_set.count()!=0:
            if not self.generate_extra(r.optional_set.get(parent_rule=r.id)):
                return False
        
        
        
        return True
    
    
    def create_rule(self,id,action,insert_num=None,log=False):
        """ Combines all the things and creates the rule you can get the final string"""
        
        #first reset if there is something from previous one
        self.reset()
        
        #set the id
        self.set_rule_id(id)
        
        if not self.__ruleid:
            print "Once bir rule_id girmeniz gerekir set_rule_id kullaniniz"
            return False
        
        try :
            r=Rule.objects.select_related().get(id=self.__ruleid)
            
        except Rule.DoesNotExist:
            print "Bu id : %s ile bir kural bulunmamaktadir "%(self.__ruleid)
            #resetting that sheet
            self.reset()
            return False
        
        #add the path of iptables
        self.__ready_rule=self.__ready_rule.strip()
        self.__ready_rule="".join([self.__ready_rule,FWALL_PATH])
        
        #add the action 
        if not self.set_rule_action(action):
            return False
        
        #set the chain
        self.set_rule_chain(r)
        
        #if it is aan insert action 
        if insert_num:
            self.__ready_rule=self.__ready_rule.strip()
            self.__ready_rule="".join([self.__ready_rule," ",str(insert_num)])
        
        
        #set the rule content
        if not self.main_gen(r):
            return False
        
        #set the target
        if not log:
            self.set_rule_target(r)
            
        else:
            #it maybe a log entry who knows ???
            if not self.create_ulog_entry(r):
                return False
        
        #print "Here we should have our ready rule..."
        
        return self.get_rule_str()
    
    
    def add_tables_path(self):
        """ That one ads the actual place of the iptables /sbin.."""
        #add the path of iptables
        self.__ready_rule=self.__ready_rule.strip()
        self.__ready_rule="".join([self.__ready_rule,FWALL_PATH])
        
    
    def set_policy(self,c_id):
        """ Sets the policies for the chains id is chain's id"""
        
        self.reset()
        
        try :
            c=Chain.objects.get(id=c_id)
            
        except Chain.DoesNotExist:
            print "Bu id : %s ile bir chain bulunmamaktadir "%(c_id)
            return False
        
        
        #the runnable path
        self.add_tables_path()
        #the action
        self.set_rule_action('policy')
        
        #the chain name setting
        self.__ready_rule=self.__ready_rule.strip()
        self.__ready_rule="".join([self.__ready_rule," ",c.name])
        
        #setting the target here
        self.__ready_rule=self.__ready_rule.strip()
        self.__ready_rule="".join([self.__ready_rule," DROP"])
        
        return self.get_rule_str()
    
    
    def create_new_chain(self,c_id,action="new"):
        """ Generate the chain according to given id
        It may also delete the chain -- added later ---"""
        
        self.reset()
        
        try :
            c=Chain.objects.get(id=c_id)
            
        except Chain.DoesNotExist:
            print "Bu id : %s ile bir chain bulunmamaktadir "%(c_id)
            return False
        
        self.add_tables_path()
        
        #the action
        self.set_rule_action(action)
        
        self.__ready_rule=self.__ready_rule.strip()
        self.__ready_rule="".join([self.__ready_rule," ",c.name])
        
        return self.get_rule_str()
    
        
        
    def create_ulog_entry(self,r):
        """Creates the log entry for the rule"""
        
        if r.prefixer_set.count()==0:
            return False
        
        #get the prefix here :
        
        p=r.prefixer_set.all()
        
        #get the name here 
        #p[0].pref_name
        #-j ULOG --ulog-nlgroup 32 --ulog-prefix foo
        
        #set the rule here
        self.__ready_rule=self.__ready_rule.strip()
        self.__ready_rule="".join([self.__ready_rule," -j ULOG --ulog-nlgroup ",NL_GROUP," --ulog-prefix ","\'",p[0].pref_name,"\'"])
        
        return True
    

class RuleFlusher(RuleGenerator):
    
    """ The one is called when a system flush is nededd"""
    
    def __init__(self):
        """ Cal teh upper one"""
        
        super(RuleFlusher,self).__init__()
        
        #the final things
        self.__chains=[]
        self.__rules={}
    
    def create_new_chains(self):
        """ Pulls from db and creates the iptables chains with -N parameter"""
        #get the new ones please
        c=[i.id for i in Chain.objects.all() if i.name not in ["INPUT","OUTPUT","FORWARD"]]
        
        for id in c:
            #reset if there is something done before
            super(RuleFlusher,self).reset()
            #add to final thing
            self.__chains.append(super(RuleFlusher,self).create_new_chain(id))
            
    
    def get_new_chains(self):
        """ It is private get it """
        if self.__chains:
            return self.__chains
        
        else:
            return False
        
    def get_ready_rules(self):
        """ Same as above one"""
        
        if self.__rules:
            return self.rules
        
        else:
            return False
        
    
    def get_loaded_rules(self,chain_name=None):
        """ Pulls the rules id's from db for appropriate chain name"""
        #get the loaded rules according to their order
        rules=Loadindex.objects.order_by('ordering')
        
        #the dictionary we will kepp the final stuff
        run_rule={}
        
        #initialize them
        for rule in rules:
            
            ch_name=rule.parent_rule.parent_chain.name
            
            if not chain_name:
                if not run_rule.has_key(ch_name):
                    run_rule[ch_name]=[]
                    
            #get the rules ids    
            #print rule.parent_rule.prefixer_set
            have_log=False
            
            #modify it for chain if we got one
            if not chain_name:
                if not rule.parent_rule.prefixer_set.count() == 0:
                    have_log=True
            
            else:
                if chain_name==ch_name:
                    if not rule.parent_rule.prefixer_set.count() == 0:
                        have_log=True
            
                
            #if we want a proper rule for that purpose
            if not chain_name:
                run_rule[ch_name].append([rule.parent_rule.id,have_log])
        
            else:
                if ch_name==chain_name:
                    if not run_rule.has_key(chain_name):
                        run_rule[chain_name]=[]
                 
                    run_rule[chain_name].append([rule.parent_rule.id,have_log])
                    
        return run_rule
    
    def create_rule_list(self,chain_name=None):
        """ The final rule list is created,we also need chain_name
        because if we flush only a 1 chain it is needded!"""
        
        #getting the loadded rules
        r_list=self.get_loaded_rules(chain_name)
        
        if not r_list:
            return
        
        self.__rules=[]
        
        #first reset the string
        
        #print r_list
        
        for chain,rule in r_list.iteritems():
            for r in rule:
                if r[1]: 
                    self.__rules.append(super(RuleFlusher,self).create_rule(r[0],"append",insert_num=None,log=True)) 
                
                self.__rules.append(super(RuleFlusher,self).create_rule(r[0],"append",insert_num=None,log=False))
            #end of the game
            
            
        
        return self.__rules
    

class IptablesRunner(object):
    """ The class will be responsible to run the methods for 
    iptables rules they will be also static"""
    
    def flush_signal(chain_name=None):
        """ runs the iptables -f and """
        
        from iptables.forms.fireutil import AktiviteKaydet
        
        #for erro checking
        errors={}
        
        
        if not chain_name:
            kurallar="".join([FWALL_PATH," ","-F"])
            
            
        else:
            kurallar="".join([FWALL_PATH," ","-F %s"%(chain_name)])
        
        #execute the flush
        execute=os.popen3("".join(["sudo ",kurallar.strip()]))
        
        if not chain_name :
            #remove the irrelevants:
            IptablesRunner.remove_irrelevant()
        
            
        error=execute[2].read() or None
        
        if error:
            errors["flush"]="".join(["Kural :",kurallar,"\n","Hata :",str(error)])
        
        
        
        ak=AktiviteKaydet()
        ak.dosya_ac()
        eylem=""
        #create rule flusher...
        rf=RuleFlusher()
        
        if not chain_name:
            eylem="".join(["Chain flush eylemi -N yeni olanlar için \n"])
            rf.create_new_chains()
            ch=rf.get_new_chains()
            
            some = 1
            #print ch 
            if ch:
                kurallar="".join([kurallar,"".join(["\n%s\n"%(i) for i in ch])])
                
                for i in ch :
                    #print "".join(["sudo ",i.strip()])
                    execute=os.popen3("".join(["sudo ",i.strip()]))
                    
                    error=execute[2].read() or None
                    
                    if error :
                        errors["".join(["hata_chain%s"%(some)])]="".join(["Kural :",i,"\n","Hata :",str(error)])
                        some=some+1
        
        
        ak.set_eylem("".join([eylem,"Bu chain flush edildi : %s \n"%(chain_name)]))    
        
        rules=rf.create_rule_list(chain_name)
        if rules:
            kurallar="".join([kurallar,"\n","".join(["%s\n"%(i) for i in rules])])
        
        ak.set_kural(kurallar)
        
        #the execution part is here :
        
        some=0
        
        if not rules:
            ak.dosya_kapa()
            return
        
        for rule in rules:
            #print "".join(["sudo ",rule.strip()])
            execute=os.popen3("".join(["sudo ",rule.strip()]))
            
            error=execute[2].read() or None
            if error:
                
                ak.set_durum("Basarisiz")
                errors["".join(["hata",str(some)])]="".join(["Kural :",rule,"\n","Hata :",str(error)])
            else:
                ak.set_durum("Basari")
                
                
            some+=1
        
        #print errors    
        ak.set_extra(hatalar=errors)
        ak.log_activity()
        ak.dosya_kapa()
        
        #hacky way of controlling the duplication of some iptables rulezzz
        
        hacky=open("hacky.txt","w")
        hacky.write("flush")
        hacky.close()
        
        #hacky ends
        #print "Executes the  rulez"
        
    flush_signal=staticmethod(flush_signal)
    
    def remove_irrelevant():
        
        from iptables.forms.fireutil import AktiviteKaydet
        import string
        
        ak=AktiviteKaydet()
        ak.dosya_ac()
        eylem="Fazla zincirleri sil"
        errors={}
        
        all_rules=os.popen3("".join(["sudo ",FWALL_PATH," -L"]))
        error=all_rules[2].read() or None
        
        if error:
            ak.set_durum("Basarisiz")
            errors["gereksiz_silme_hatasi"]="".join(["Kural : Fazla chain silerken hata cikti!"])
        else:
            ak.set_durum("Basarili")
        
        all_rules=all_rules[1].read()
        
        if all_rules:
            all_rules=all_rules.split('\n')
            
            for ch in all_rules:
                if string.find(ch,'Chain')!=-1:
                    if ch.split()[1] not in ["INPUT","OUTPUT","FORWARD"]:
                        #print "".join(["sudo ",FWALL_PATH," -X ",ch.split()[1].strip()])
                        remove_action=os.popen3("".join(["sudo ",FWALL_PATH," -F"]))
                        remove_action=os.popen3("".join(["sudo ",FWALL_PATH," -X ",ch.split()[1].strip()]))
                        
                        error=remove_action[2].read() or None
                        if error:
                            errors[str(ch.split()[1])]="".join(["Kural :\n",str(ch.split()[1]),"\n Fazla chain silerken hata cikti!\n",error])
                            
        #final logs
        ak.set_eylem(eylem)
        ak.set_extra(hatalar=errors)
        ak.log_activity()
        ak.dosya_kapa()
        
    remove_irrelevant=staticmethod(remove_irrelevant)
    
    def escape_plan():
        """ That method is if the application crashes somehow or a 
        db error occured. Therefore the if Loadindex ordering is corrupted
        it should be executed !"""
        
        from iptables.dbwork.ruleorder import RuleLoadOrder
        from iptables.models import Loadindex
        
        #remove all the rules from Loadindex
        loader=Loadindex.objects.all()
        
        for load_rule in loader:
            r=RuleLoadOrder()
            del_res=r.delete_order(load_rule.parent_rule)
        
        #print "Load index is empty now."
        
        #load all rules that are Loaded True
        rules=Rule.objects.filter(loaded=True)
        
        for l_r in rules:
            r=RuleLoadOrder()
            #print str(l_r.id)
            #print l_r.name
            r.insert_order(str(l_r.id),ordernum='last')
            
        #print "Rules loaded again fixed!"
        
        #flush the iptables
        IptablesRunner.flush_signal()
        
        #print "Rules loaded into memory situation fixed!"
        
    #yes it is static of course...
    escape_plan=staticmethod(escape_plan)
        
    
